using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using System.Collections.ObjectModel;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Text;
using System.Collections.Generic;
using System.Diagnostics;

namespace VetumaForSP2010.Features.VetumaForSPFeature
{
    /// <summary>
    /// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade.
    /// </summary>
    /// <remarks>
    /// The GUID attached to this class may be used during packaging and should not be modified.
    /// </remarks>
    /// 

    [Guid("d33501d1-902d-452e-8873-4754db9386ce")]
    public class VetumaForSPFeatureEventReceiver : SPFeatureReceiver
    {
        // Uncomment the method below to handle the event raised after a feature has been activated.

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPWebApplication webApp = (SPWebApplication)properties.Feature.Parent;
            try
            {
                // Check is claims authentication enabled.
                if (webApp.UseClaimsAuthentication)
                {
                    // Get configuration properties created with VetumaForSharePointConfigurationApp
                    ConfigurationProperties props = getConfigurationProperties();

                    // Create web.config entries.
                    createAndSaveEntries(webApp, props);

                    // Create VfspList.
                    createVfspList(webApp);
                }
            }
            catch (Exception ex)
            {
                DataAccess.logError(ex, EventLogEntryType.Error);
            }
        }

        // Uncomment the method below to handle the event raised before a feature is deactivated.

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPWebApplication webApp = (SPWebApplication)properties.Feature.Parent;
            try
            {
                removeModifications(webApp);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error on feature deactivation: " + ex.Message);
                DataAccess.logError(ex, EventLogEntryType.Error);
            }
        }


        // Uncomment the method below to handle the event raised after a feature has been installed.

        //public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        //{
        //}


        // Uncomment the method below to handle the event raised before a feature is uninstalled.

        //public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
        //{
        //}

        // Uncomment the method below to handle the event raised when a feature is upgrading.

        //public override void FeatureUpgrading(SPFeatureReceiverProperties properties, string upgradeActionName, System.Collections.Generic.IDictionary<string, string> parameters)
        //{
        //}


        private ConfigurationProperties getConfigurationProperties()
        {
            string path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + "\\Vetuma for SharePoint\\VetumaForSharePoint.xml";

            ConfigurationProperties props = null;

            if (File.Exists(path))
            {
                using (StreamReader sr = new StreamReader(path, Encoding.UTF8, false))
                {
                    XmlReader reader = new XmlTextReader(sr);
                    XmlSerializer serializer = new XmlSerializer(typeof(ConfigurationProperties));

                    props = serializer.Deserialize(reader) as ConfigurationProperties;

                    sr.Close();
                };
            }
            else
            {
                throw new ArgumentException("Configuration file missing. Please run VetumaForSharePointConfigurationApp first.");
            }

            return props;
        }

        /// <summary>
        /// Create list for holding hashed social security numbers and encrypted ad-passwords.
        /// </summary>
        /// <param name="webApp"></param>
        private void createVfspList(SPWebApplication webApp)
        {
            SPSiteCollection sites = webApp.Sites;

            string listName = "VfspList";

            // Loop each site collection.
            for (int ii = 0; ii < sites.Count; ii++)
            {
                try
                {
                    using (SPSite site = sites[ii])
                    {
                        // Handle root web.
                        using (SPWeb web = site.RootWeb)
                        {
                            // Check does user have rights to add list.
                            if (web.DoesUserHavePermissions(SPBasePermissions.ManageLists))
                            {
                                // Check does VfspList exist.
                                if (!webContainsList(web, listName))
                                {
                                    // Add list.
                                    Guid listId = web.Lists.Add(listName, "", SPListTemplateType.GenericList);

                                    SPList list = web.Lists[listId];
                                    list.OnQuickLaunch = false;
                                    list.Hidden = true; // Hide from users.
                                    list.AllowDeletion = false;
                                    list.NoCrawl = true; // No need for crawling because data only for Vetuma for SharePoint use.

                                    // Add necessary fields.
                                    list.Fields.Add("VfspUserName", SPFieldType.Text, false);
                                    list.Fields.Add("VfspSoc", SPFieldType.Text, false);
                                    list.Fields.Add("VfspPas", SPFieldType.Text, false);

                                    // Update changes to list.
                                    list.Update();

                                    // Remove other that Full mask rights.
                                    list.BreakRoleInheritance(true);

                                    // Get role assignments to loop.
                                    SPRoleAssignmentCollection assignments = list.RoleAssignments;

                                    // Start from end because assignments can be removed while looping.
                                    for (int jj = assignments.Count - 1; jj >= 0; jj--)
                                    {
                                        // Assignment will be deleted.
                                        bool delete = true;

                                        foreach (SPRoleDefinition r in assignments[jj].RoleDefinitionBindings)
                                        {
                                            // Check does role assignment have full mask rights
                                            if (r.BasePermissions == SPBasePermissions.FullMask)
                                            {
                                                // Role assignment has Full mask permissions so it will not be deleted.
                                                delete = false;
                                                break;
                                            }
                                        }

                                        if (delete)
                                        {
                                            // Role assignment permissions did not contain full mask so it will be deleted.
                                            assignments.Remove(jj);
                                        }
                                    }
                                }
                            }
                            else
                            {
                                DataAccess.logError(new ApplicationException(string.Format("User does not have rights to add lists to web {0} at site {1}. If Vetuma for SharePoint is not used in this site, no actions needed.",
                                    web.Name, site.Url)), EventLogEntryType.Warning);
                            }
                        };
                    };
                }
                catch (Exception ex)
                {
                    DataAccess.logError(ex, EventLogEntryType.Error);
                }
            }
        }

        /// <summary>
        /// Checks does web contain list.
        /// </summary>
        /// <param name="web">Web-object.</param>
        /// <param name="listName">List to find.</param>
        /// <returns></returns>
        internal static bool webContainsList(SPWeb web, string listName)
        {
            SPListCollection webLists = web.Lists;

            foreach (SPList list in webLists)
            {
                if (list.Title.Equals(listName))
                {
                    return true;
                }
            }

            return false;
        }


        /// <summary>
        /// Removes web.config modifications owned by VetumaForSharePoint.
        /// </summary>
        /// <param name="webApp"></param>
        private void removeModifications(SPWebApplication webApp)
        {
            string owner = "VetumaForSharePoint";
            Collection<SPWebConfigModification> modifications = webApp.WebConfigModifications;

            int startCount = modifications.Count;

            for (int ii = startCount - 1; ii >= 0; ii--)
            {
                if (modifications[ii].Owner == owner)
                {
                    modifications.Remove(modifications[ii]);
                }
            }

            // If some modifications are removed update web.configs.
            if (startCount > modifications.Count)
            {
                webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
                webApp.Update();
            }
        }

        /// <summary>
        /// Creates web.config modifications for Vetuma for Sharepoint and saves them.
        /// </summary>
        /// <param name="webApp"></param>
        private void createAndSaveEntries(SPWebApplication webApp, ConfigurationProperties props)
        {
            string provider = "";
            string providerWebConfigString = "";

            if (props.UserStore == UserStore.SQL)
            {
                provider = "VFSPSqlMembershipProvider";
                providerWebConfigString = string.Format("<add name=\"{0}\" connectionStringName=\"VFSPSqlConnString\" enablePasswordRetrieval=\"true\" enablePasswordReset=\"true\" requiresQuestionAndAnswer=\"true\" applicationName=\"/\" requiresUniqueEmail=\"false\" passwordFormat=\"Encrypted\" maxInvalidPasswordAttempts=\"3\" minRequiredPasswordLength=\"7\" minRequiredNonalphanumericCharacters=\"1\" passwordAttemptWindow=\"10\" passwordStrengthRegularExpression=\"\" type=\"System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" />", provider);
            }
            else
            {
                provider = "VFSPActiveDirectoryMembershipProvider";
                providerWebConfigString = string.Format("<add name=\"{0}\" type=\"System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" connectionStringName=\"VFSPAdConnString\" connectionUsername=\"{1}\" connectionPassword=\"{2}\" connectionProtection=\"Secure\" enablePasswordReset=\"true\" enableSearchMethods=\"true\" requiresQuestionAndAnswer=\"true\" applicationName=\"/\" description=\"Default AD connection\" requiresUniqueEmail=\"false\" clientSearchTimeout=\"30\" serverSearchTimeout=\"30\" attributeMapPasswordQuestion=\"department\" attributeMapPasswordAnswer=\"division\" attributeMapFailedPasswordAnswerCount=\"revision\" attributeMapFailedPasswordAnswerTime=\"badPasswordTime\" attributeMapFailedPasswordAnswerLockoutTime=\"maxStorage\" attributeMapEmail=\"mail\" attributeMapUsername=\"sAMAccountName\" maxInvalidPasswordAttempts=\"5\" passwordAttemptWindow=\"10\" passwordAnswerAttemptLockoutDuration=\"30\" minRequiredPasswordLength=\"7\" minRequiredNonalphanumericCharacters=\"1\" passwordStrengthRegularExpression=\"{3}\" />",
                                                        provider, props.UserNameForAd, props.PasswordForAd, "(?=.{6,})(?=(.*\\d){1,})(?=(.*\\W){1,})");
            }



            List<ModificationEntry> entries = new List<ModificationEntry>() { 
              new ModificationEntry("section[@name='VetumaForSharePointConfig']","configuration/configSections","<section name=\"VetumaForSharePointConfig\" type=\"VetumaForSP2010.Configuration.VetumaForSharePointConfig, VetumaForSP2010, Version=1.0.0.0, Culture=neutral, PublicKeyToken=64002903817359f3\" />", SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode),
              new ModificationEntry("VetumaForSharePointConfig","configuration","<VetumaForSharePointConfig></VetumaForSharePointConfig>", SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode),
              new ModificationEntry("VetumaConfig","configuration/VetumaForSharePointConfig", string.Format("<VetumaConfig SharedSecretId=\"{0}\" SharedSecret=\"{1}\" ConfigurationId=\"{2}\" PostUrl=\"{3}\" SupportedAuthenticationMethods=\"tupas\" ApplicationId=\"{4}\" />", 
                                                                                                        props.SharedSecredId, props.SharedSecred, props.ConfigurationId, props.PostUrl, props.ApplicationId), SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode),
              new ModificationEntry("RegistrationConfig","configuration/VetumaForSharePointConfig", string.Format("<RegistrationConfig UserStore=\"{0}\" VFSKey=\"{1}\" Provider=\"{2}\" />", 
                                                                                                        props.UserStore, props.Key, provider), SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode),
              new ModificationEntry("SharePointGroups","configuration/VetumaForSharePointConfig","<SharePointGroups></SharePointGroups>", SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode),
              new ModificationEntry("connectionStrings","configuration","<connectionStrings></connectionStrings>", SPWebConfigModification.SPWebConfigModificationType.EnsureSection),
              new ModificationEntry("add[@name='VFSPSqlConnString']", "configuration/connectionStrings", string.Format("<add name=\"VFSPSqlConnString\" connectionString=\"{0}\" providerName=\"System.Data.SqlClient\" />", props.SqlConnectionString), SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode),
              new ModificationEntry(string.Format("add[@name='{0}']", provider),"configuration/system.web/membership/providers",providerWebConfigString, SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode),
              new ModificationEntry("add[@name='VFSPRoleProvider']","configuration/system.web/roleManager/providers","<add name=\"VFSPRoleProvider\" connectionStringName=\"VFSPSqlConnString\" applicationName=\"/\" description=\"Stores and retrieves roles from SQL Server\" type=\"System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" />", SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode)
                                            };

            foreach (string groupName in props.SharePointGroups)
            {
                entries.Add(new ModificationEntry(string.Format("add[@GroupName='{0}']", groupName), "configuration/VetumaForSharePointConfig/SharePointGroups", string.Format("<add GroupName=\"{0}\" />", groupName), SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode));
            }

            if (props.UserStore == UserStore.AD)
            {
                entries.Add(new ModificationEntry("add[@name='VFSPAdConnString']", "configuration/connectionStrings", string.Format("<add name=\"VFSPAdConnString\" connectionString=\"{0}\" />", props.ADConnectionString), SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode));
            }

            // Save entries.
            saveEntries(entries, webApp);
        }

        /// <summary>
        /// Saves entries to web application web.config.
        /// </summary>
        /// <param name="entries"></param>
        /// <param name="webApp"></param>
        private void saveEntries(List<ModificationEntry> entries, SPWebApplication webApp)
        {
            string webAppId = webApp.Id.ToString();
            // Loop each entry in entries and add
            // it to webConfigModifications.
            foreach (ModificationEntry e in entries)
            {
                webApp.WebConfigModifications.Add(createModification(e, webAppId));
            }

            // Apply changes.
            webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();

            // Save changes.
            webApp.Update();
        }

        /// <summary>
        /// Creates SPWebConfigModification by internal modification entry.
        /// </summary>
        /// <param name="entry"></param>
        /// <param name="webAppId"></param>
        /// <returns></returns>
        private SPWebConfigModification createModification(ModificationEntry entry, string webAppId)
        {
            SPWebConfigModification modification = new SPWebConfigModification(entry.Name, entry.XPath);
            // Owner individualizes modifications so it will be 
            // easy to remove in the future with owner's name. 
            modification.Owner = "VetumaForSharePoint";
            modification.Sequence = 0;
            modification.Type = entry.Type;
            modification.Value = entry.Value;

            return modification;
        }

        /// <summary>
        /// Internal modification entry struct.
        /// </summary>
        private struct ModificationEntry
        {
            public string Name;
            public string XPath;
            public string Value;
            public SPWebConfigModification.SPWebConfigModificationType Type;

            public ModificationEntry(string name, string xpath, string value, SPWebConfigModification.SPWebConfigModificationType type)
            {
                this.Name = name;
                this.XPath = xpath;
                this.Value = value;
                this.Type = type;
            }
        }
    }
}
